#include "device.h"
#include "common/sdl.h"
#include "common/win.h"
#include "common/x11.h"
#include "common/xf86.h"
#include <iostream>

namespace sleek
{
    namespace core
    {
        namespace device
        {
            Device* CreateDeviceWindowManager(DeviceWindowManager type, Device_stub info)
            {
                switch(type)
                {
                    case DWM_AUTO:
                        #if defined x11_device_support
                            return new Device_x11(info);
                        #elif defined win_device_support
                            return new Device_win(info);
                        #else
                            return new Device_sdl(info);
                        #endif
                    break;
                    #if defined sdl_device_support
                        case DWM_SDL: return new Device_sdl(info); break;
                    #endif
                    #if defined x11_device_support
                        case DWM_X11: return new Device_x11(info); break;
                    #endif
                    #if defined xf86_device_support
                        case DWM_XF86: return new Device_xf86(info); break;
                    #endif
                    #if defined win_device_support
                        case DWM_WIN: return new Device_win(info); break;
                    #endif
                }
                return 0;
            }
            Device::Device()
            {
                fps = stack = 500;
                glcurrent = GRC_0;
                reading = true;
                current = new input();
                ext = new event();
                evt = ext;
                tm = new math::timer();
            }
            Device::Device(Device_stub a)
            {
                fps = stack = 500;
                glcurrent = GRC_0;
                reading = true;
                current = new input();
                ext = new event();
                evt = ext;
                info = a;
                tm = new math::timer();
            }
            void Device::initOpenCL()
            {
                ocl = 0;
            }
            void Device::initOpenGL()
            {
                if(checkOpenGLExtension("GL_ARB_multitexture"))
                {
                    int texture_unity = GL_MAX_TEXTURE;
                    glGetIntegerv(GL_MAX_TEXTURE_UNITS, &texture_unity);
                    glGetIntegerv(GL_MAX_TEXTURE_UNITS_ARB,&texture_unity);
                    printf("Max texture units initialized: %d\n", texture_unity);
                }
                else printf("GL_ARB_multitexture: test fail\n");
            }
            bool Device::checkOpenGLExtension(const char *name)
            {
                const GLubyte *extensions = NULL;
                const GLubyte *debut;
                GLubyte *place, *fin;

                place = (GLubyte *) strchr ( name, ' ' );
                if ( place || *name == '\0' ) return GL_FALSE;

                if ( (extensions = glGetString ( GL_EXTENSIONS )) == NULL ) { printf("glGetString fail\n"); return GL_FALSE; }
                debut = extensions;
                for (;;)
                {
                    place = (GLubyte *) strstr((const char *) debut, name );
                    if (!place) break;
                    fin = place + strlen ( name );
                    if ( place == debut || *(place - 1) == ' ' )
                        if (*fin == ' ' || *fin == '\0')
                            return 1;
                    debut = fin;
                }
                return 0;
            }
            void Device::welcomeMessage()
            {
                printf("=========================================================================\n");
                printf("\t\t\t\tSleekThink %s\n", version);
                printf("=========================================================================\n");
                #if defined __linux
                    core::os::stringc linuxversion;
                    struct utsname LinuxInfo; uname(&LinuxInfo);
                    linuxversion += LinuxInfo.sysname; linuxversion += " "; linuxversion += LinuxInfo.machine;
                    linuxversion += "  Kernel "; linuxversion += LinuxInfo.release;
                    linuxversion += LinuxInfo.version; linuxversion.print();
                #elif defined WIN32 || defined WIN64
                #endif
                cpu = new os::cpuid();
                gpu = new os::gpuid();
                ppu = new os::ppuid();

                //printf("\n");
                cpu->detectVirtualProcessor();
                gpu->detectVirtualProcessor();
                ppu->detectVirtualProcessor();
                cpu->printQuickDescription();
                gpu->printQuickDescription();
                ppu->printQuickDescription();
                //printf("\n");

                printf("OpenGL version: %s\n",(char*)glGetString(GL_VERSION));
                printf("OpenGL render: %s\n",(char*)glGetString(GL_RENDERER));
                printf("OpenGL vendor: %s\n",(char*)glGetString(GL_VENDOR));

                #if defined GL_VERSION_2_0 || defined GL_ARB_shader_objects
                    if(checkOpenGLExtension("GL_ARB_shader_objects"))
                    {
                        printf("GLSL available: ");
                        if(checkOpenGLExtension("GL_ARB_vertex_shader")) printf("vertex");
                        if(checkOpenGLExtension("GL_ARB_fragment_shader")) printf("/fragment");
                        #if defined GL_ARB_geometry_shader4 || defined GL_EXT_geometry_shader4 || defined GL_NV_geometry_program4 || defined GL_NV_geometry_shader4
                            if(checkOpenGLExtension("GL_ARB_geometry_shader4") || checkOpenGLExtension("GL_EXT_geometry_shader4") || checkOpenGLExtension("GL_NV_geometry_program4") || checkOpenGLExtension("GL_NV_geometry_shader4"))
                                printf("/geometry\n");
                            else printf("\n");
                        #else
                            printf("\n");
                        #endif
                    }
                    else
                    {
                        printf("GLSL extention not found\n");
                    }
                #else
                    printf("GLSL extention not compiled\n");
                #endif
                printf("=========================================================================\n");
                initOpenGL();
            }
            os::cpuid* Device::getProcessorCentral()
            {
                return cpu;
            }
            os::gpuid* Device::getProcessorGraphics()
            {
                return gpu;
            }
            os::ppuid* Device::getProcessorPhysics()
            {
                return ppu;
            }
            Device::~Device()
            {
                exit();
                if(evt) delete evt;
            }
            void Device::WarpMouse(math::vector2di)
            {
            }
            void Device::ShowMouse(bool)
            {
            }
            void Device::setCaption(os::stringc name)
            {
                info.name = name;
            }
            u32 Device::getFPS()
            {
                return fps;
            }
            Device_stub Device::getInfo()
            {
                return info;
            }
            void Device::enableAntialiasing(DeviceAntiailiasingMode i)
            {
                info.antialiasing = i;
                switch(i)
                {
                    case DAM_FAST:
                        glHint(GL_FOG_HINT, GL_FASTEST);
                        glHint(GL_LINE_SMOOTH_HINT, GL_FASTEST);
                        glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_FASTEST);
                        glHint(GL_POINT_SMOOTH_HINT, GL_FASTEST);
                        glHint(GL_POLYGON_SMOOTH_HINT, GL_FASTEST);
                    break;
                    case DAM_NICE:
                        glHint(GL_FOG_HINT, GL_NICEST);
                        glHint(GL_LINE_SMOOTH_HINT, GL_NICEST);
                        glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);
                        glHint(GL_POINT_SMOOTH_HINT, GL_NICEST);
                        glHint(GL_POLYGON_SMOOTH_HINT, GL_NICEST);
                    break;
                    default:
                        glHint(GL_FOG_HINT, GL_DONT_CARE);
                        glHint(GL_LINE_SMOOTH_HINT, GL_DONT_CARE);
                        glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_DONT_CARE);
                        glHint(GL_POINT_SMOOTH_HINT, GL_DONT_CARE);
                        glHint(GL_POLYGON_SMOOTH_HINT, GL_DONT_CARE);
                    break;
                }
            }
            void Device::enableWindowDecorator(bool z)
            {
                info.decorator = z;
            }
            bool Device::run()
            {
                return running;
            }
            bool Device::testGlError()
            {
                GLenum g = glGetError();
                if(g != GL_NO_ERROR) printf("GL: %s\n", gluErrorString(g));
                switch(g)
                {
                    case GL_INVALID_ENUM:      return true; break;
                    case GL_INVALID_VALUE:     return true; break;
                    case GL_INVALID_OPERATION: return true; break;
                    case GL_STACK_OVERFLOW:    return true; break;
                    case GL_STACK_UNDERFLOW:   return true; break;
                    case GL_OUT_OF_MEMORY:     return true; break;
                    #if defined GL_EXT_framebuffer_object
                        case GL_INVALID_FRAMEBUFFER_OPERATION_EXT: return true; break;
                    #endif
                };
                return false;
            }
            texture::texture* Device::createScreeshot()
            {
                texture::texture *tmp = new texture::texture(info.size,texture::TXFMT_RGB);
                glReadBuffer(GL_FRONT);
                glReadPixels(0,0,info.size.width, info.size.height, GL_RGB, GL_UNSIGNED_BYTE, tmp->buffer);
                glReadBuffer(GL_BACK);
                return tmp;
            }
            void Device::WriteConfig(core::os::stringc file)
            {
                Config *tmp = new Config();
                    tmp->setFullScreen(info.fullscreen);
                    tmp->setScreenSize(info.size);
                    tmp->setScreenBits(info.bits);
                    tmp->write(file);
                delete tmp;
            }
            void Device::centerWindowPos()
            {
                core::math::vector2du desk = (getDesktopVideoSize()-getInfo().size)/2;
                setWindowPos(core::math::vector2di(desk.width, desk.height));
            }
            void Device::makeRenderContext(GL_RENDER_CONTEXT)
            {
                testGlError();
            }
            void Device::destroyRenderContext(GL_RENDER_CONTEXT)
            {
                testGlError();
            }
            void Device::switchToRenderContext(GL_RENDER_CONTEXT i)
            {
                glcurrent = i;
                testGlError();
            }
            GL_RENDER_CONTEXT Device::getCurrentRenderContext()
            {
                return glcurrent;
            }
            input* Device::getCurrentEvent()
            {
                return current;
            }
            void Device::setEventReceiver(event *a)
            {
                if(a) evt = a;
                else evt = ext;
            }
            void Device::setVideoSize(math::vector2du i)
            {
                info.size = i;
                glViewport(0, 0, i.width, i.height);

                glMatrixMode(GL_PROJECTION);
                glLoadIdentity();

                glFrustum(-2.25f,-2.25f, -1.5f, 1.5f, -1.f,1.f);
                #ifndef WIN32 || WIN64
                    glOrtho(0,info.size.width,info.size.height,0,-1.f,1.f);
                    glViewport(0,0,info.size.width,info.size.height);
                #else
                    glOrtho(0,info.size.width,0,info.size.height,-1.f,1.f);
                    glViewport(0,0,info.size.width,info.size.height);
                #endif

                glMatrixMode(GL_MODELVIEW);
                glLoadIdentity();

                enableAntialiasing(info.antialiasing);
            }
            void Device::setWindowPos(math::vector2di)
            {
            }
            void Device::setGamma(texture::pixel c)
            {
            }
            void Device::setFullScreen(bool f)
            {
            }
            event *Device::getEventReceiver()
            {
                return evt;
            }
            math::vector2du Device::getDesktopVideoSize()
            {
            }
            cl::Context* Device::getOpenCL()
            {
                return ocl;
            }
            void Device::begin(texture::pixel color)
            {
                tm->update_begin();
                glClearColor((f32)(color.red/255.f),(f32)(color.green/255.f),(f32)(color.blue/255.f),(f32)(color.alpha/255.f));
                glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
                glPushMatrix();
                glStencilFunc(GL_ALWAYS, 0x0, 0x4);
                glStencilMask(0x4);
                glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE);
            }
            bool Device::manage()
            {
                evt->manage(current);
                current->clear();
                running = evt->asRunning;
                return false;
            }
            bool Device::ready()
            {
                return reading;
            }
            void Device::end()
            {
                glPopMatrix();
                glFlush();
                if(tm->getElapsedTime() >= 1000)
                {
                    fps = stack-1;
                    stack = 1;
                    tm->update_end();
                } else stack++;
            }
            void Device::exit()
            {
                reading = false;
                running = false;
            }
        }
    }
}
